﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Silvermoon.Visuals;
using Silvermoon.Core;
using Silvermoon.Controls.Interfaces;
using System.Diagnostics;
using Silvermoon.Controls.Classes;
using Silvermoon.OpenGL;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Base class for all Controls.
    /// </summary>
    public abstract class Control : IDisposable, IVisualControl, IControlAnimation
    {
        #region ctor/dtor

        public Control()
            : base()
        {
            InvalidationMask = InvalidationFlag.All;
        }

        public virtual void Dispose()
        {
            if (Disposed != null) Disposed(this, EventArgs.Empty);
            if (Visuals != null) foreach (Visual visual in Visuals) visual.Dispose();
            if (SubControls != null)
            {
                foreach (Control c in this.SubControls) c.Dispose();
            }
        }


        #endregion
        #region Fields

        private ControlFlags flags = ControlFlags.None;
        private List<Transformation> animTransforms;
        protected Color textColor = Color.Empty;
        private bool visible = true;
        protected Margin clipping;
        protected Point location;
        protected Size size;
        private int alpha = 255;
        private bool enabled = true;
        private Rectangle clientBounds;
        private Margin margin;

        /// <summary>
        /// Gets a list of possible child controls.
        /// </summary>
        /// <remarks>
        /// Previousely this was implemented as an interface of an explicitely implemented inteface (IPrimitive) to hide this property
        /// for public access, but allow the <see cref="Renderer"/> to access this property as interface.
        /// But due to the fact, that casting is time expensive, I abandoned this elegant pattern and declared this property as internal.
        /// Performance wins!
        /// </remarks>
        protected internal virtual IList<Control> SubControls { get { return null; } }

        #endregion
        #region Properties

        public ControlFlags Flags { get { return flags; } set { flags = value; } }

        /// <summary>
        /// Gets whether the control has been created which means it has been initialized once.
        /// This value returns false while the control is being created but not rendered.
        /// </summary>
        /// <remarks>
        /// This flags helps animations to decide wether to perform an animation. If this value returns false, no animation should be performed, 
        /// as the control itself is not even visible.
        /// </remarks>
        public bool Created { get { return !CheckFlag(InvalidationFlag.Uninitialized); } }

        /// <summary>
        /// Gets whether the control has animations running.
        /// </summary>
        /// <value></value>
        public bool IsAnimated { get { return animTransforms != null && animTransforms.Count > 0; } }

        /// <summary>
        /// Gets what parts are invalidated.
        /// </summary>
        public InvalidationFlag InvalidationMask { get; private set; }


        /// <summary>
        /// Gets or sets the alpha value for the control in a range between 0 and 255 where 0 is transparent and 255 opaque.
        /// </summary>
        public  int Alpha { get { return alpha; } set { alpha = ValidateAlpha(value); Invalidate(InvalidationFlag.Alpha); } }

        protected int ValidateAlpha(int value)
        {
            if (value < 0) return 0;
            if (value > 255) return 255;
            return value;
        }

        public bool VisibleAndEnabled { get { return Enabled && visible; } }

        public bool Enabled { get { return enabled; } set { if (enabled != value) { enabled = value; OnEnabledChanged(); } } }

        //        public Rectangle RenderBounds { get { return Bounds; } }

        public IEnumerable<Transformation> Transformations { get; set; }

        /// <summary>
        /// Gets all visuals for rendering. Note that all <see cref="Visual"/>s at this list get also disposed when this control is disposed.
        /// </summary>
        public virtual IEnumerable<Visual> Visuals { get; protected set; }

        public Point Location
        {
            get { return location; }
            set
            {
                if (location != value)
                {
                    location.X = value.X;
                    location.Y = value.Y;
                    OnLocationChanged();
                }
            }
        }

        public int ClientHeight { get { return clientBounds.Height; } }
        public int ClientWidth { get { return clientBounds.Width; } }
        public int ClientLeft { get { return clientBounds.Left; } }
        public int ClientTop { get { return clientBounds.Top; } }

        public int Width
        {
            get { return size.Width; }
            set
            {
                if (size.Width != value)
                {
                    size.Width = value;
                    OnSizeChanged();
                }
            }
        }

        public int Height
        {
            get { return size.Height; }
            set
            {
                if (size.Height != value)
                {
                    size.Height = value;
                    OnSizeChanged();
                }
            }
        }

        public Size Size
        {
            get { return size; }
            set
            {
                if (size != value)
                {
                    size.Width = value.Width;
                    size.Height = value.Height;
                    OnSizeChanged();
                }
            }
        }


        /// <summary>
        /// Gets the actual clipping bounds which depend on animations.
        /// </summary>
        public virtual Rectangle ActualBounds
        {
            get { return new Rectangle(location.X, location.Y, size.Width, size.Height); }
            set { }
        }

        public int X { get { return location.X; } }
        public int Y { get { return location.Y; } }
        public int Left { get { return location.X; } }
        public int Top { get { return location.Y; } }
        public int Right { get { return location.X + size.Width; } }
        public int Bottom { get { return location.Y + size.Height; } }

        ///// <summary>
        ///// Gets whether a control has changed it's surface and must be rendered again.
        ///// </summary>
        //public bool IsInitialized { get { return invalidMask == InvalidationFlag.None; } }

        /// <summary>
        /// Gets whether a control has become invalidated.
        /// </summary>
        public bool Invalidated { get { return InvalidationMask != InvalidationFlag.Empty; } }

        public Screen Screen { get { return Screen.Current; } }

        public Rectangle Bounds
        {
            get { return new Rectangle(location.X, location.Y, size.Width, size.Height); }
            set
            {
                if (size.Width != value.Width || size.Height != value.Height)
                {
                    size.Width = value.Width > 0 ? value.Width : 0;
                    size.Height = value.Height > 0 ? value.Height : 0;
                    OnSizeChanged();
                }
                if (location.X != value.X || location.Y != value.Y)
                {
                    location.X = value.X;
                    location.Y = value.Y;
                    OnLocationChanged();
                }
            }
        }

        /// <summary>
        /// Gets the rectangle of the client bounds of the control. this is the rectangle 0,0,Width,Height including
        /// the values of <seealso cref="Margin"/>.
        /// </summary>
        public Rectangle ClientBounds
        {
            get { return clientBounds; }
            protected set { clientBounds = value; }
        }

        public Margin Margin
        {
            get { return margin; }
            set { margin = value; }
        }

        /// <summary>
        /// Specifies the clipping margins for the control. The Clipping defines the ClientBounds of the control by adding it's values to the corners.
        /// If used on a control that has child controls, the Clipping also defines the valid rectangle for rendering the child controls.
        /// </summary>
        public Margin Clipping
        {
            get { return clipping; }
            set { clipping = value; }
        }

        public virtual bool Visible
        {
            get { return visible; }
            set
            {
                visible = value;
                OnVisibleChanged();
            }
        }

        /// <summary>
        /// Gets or set the text color for this and all child controls, if no child control has specified a TextColor other than Color.Empty.
        /// </summary>
        /// <remarks>
        /// This property is kinda special. It is not only bound to the owner control but to all child controls. This allows a host control to specify and change the text color
        /// for all child controls at once. for instance, imagine a ListView which has a ListViewItem. If the ListViewItem changed it's IsSelected property, the background color
        /// changes. In this case the text color of all child controls need to be changed either from black to white, since the contrast between black and blue (which is the default 
        /// selected ListViewItem color) would not be appropriate.
        /// </remarks>
        public Color TextColor
        {
            get { return textColor; }
            set
            {
                if (textColor != value)
                {
                    textColor = value;
                    Invalidate(InvalidationFlag.TextColor);
                    OnTextColorChanged();
                }
            }
        }

        /// <summary>
        /// Gets whether either this control or any child control has the current focus.
        /// </summary>
        public virtual bool HasFocus
        {
            get { return Screen.Current.HasFocus(this); }
        }

        /// <summary>
        /// Gets whether the control has transformations assigned.
        /// </summary>
        public bool HasTransformations { get { return Transformations != null; } }

        /// <summary>
        /// Used by the <see cref="LayoutEngine"/> to set the actual bounds for the control, depending on <see cref="Margin"/>s and clipping to parent controls.
        /// </summary>
        //public Rectangle RenderBounds { get; internal set; }

        #endregion
        #region Events

        protected virtual void OnLocationChanged()
        {
            Invalidate(InvalidationFlag.Location);
            if (LocationChanged != null) LocationChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs when the size of the control has changed.
        /// Note: avoid to use this method to adjust the size or location of child controls or visuals. Rather use InitializeControls for that purpose.
        /// This is because OnSizeChanged would always be executed, while InitializeControl occurs only on demand. For instance, changing the Size of a control
        /// 5 time would call 5 times OnSizeChanged but only 1 time InitializeControls!
        /// </summary>
        protected virtual void OnSizeChanged()
        {
            Invalidate(InvalidationFlag.Size);
            if (SizeChanged != null) SizeChanged(this, EventArgs.Empty);
        }

        #endregion
        #region Methods

        protected virtual void OnVisibleChanged()
        {
            // when a control is not visible, it'l never change it's IsInitialized state, thus it must be changed at this place to ensure that it will be rendered after it has become visible:
            //   if (!Visible && HasFocus) Screen.Current.Focus(null);
            if (visible) InvalidationMask &= ~InvalidationFlag.Visible;
            Invalidate(InvalidationFlag.Visible);
            if (VisibleChanged != null) VisibleChanged(this, EventArgs.Empty);

        }

        protected virtual void OnEnabledChanged()
        {
            Invalidate(InvalidationFlag.Enabled);
            if (EnabledChanged != null) EnabledChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs directly before OnLayout. This is useful to ensure that some values are initialized on overridden OnLayout
        /// methods.
        /// </summary>
        protected virtual void PreLayout()
        {
            if ((InvalidationMask & (InvalidationFlag.Size | InvalidationFlag.Margin)) != 0)
            {
                clientBounds = new Rectangle(0, 0, size.Width, size.Height);
                Margin m = clipping;
                int l = m.LeftZero;
                int t = m.TopZero;
                clientBounds.Offset(l, t);
                clientBounds.Width -= l + m.RightZero;
                clientBounds.Height -= t + m.BottomZero;

            }
        }

        /// <summary>
        /// Occurs when to layout all visuals.
        /// </summary>
        protected virtual void OnLayout()
        {
        }

        /// <summary>
        /// Occurs after PreLoad.
        /// </summary>
        protected virtual void Loaded()
        {
        }


        /// <summary>
        /// Occurs once when the control is initialized.
        /// </summary>
        protected virtual void Initialized()
        {
        }

        /// <summary>
        /// Preload asures that possible rendering and data calculating is done. Calling PreLoad is a good idea to execute
        /// against each control that is not currently in the visible view to asure a fluent animation.
        /// </summary>
        /// <remarks>
        /// As a control developer, you need to assure that you preload any data by overriding PreLoad.
        /// </remarks>
        public virtual void PreLoad()
        {
            if (Invalidated)
            {
                if (CheckFlag(
                    InvalidationFlag.Transformation |
                    InvalidationFlag.Visible |
                    InvalidationFlag.Appearance |
                    InvalidationFlag.Color |
                    InvalidationFlag.Custom |
                    InvalidationFlag.Enabled |
                    InvalidationFlag.LayoutAppearance |
                    InvalidationFlag.Margin | InvalidationFlag.Padding |
                    InvalidationFlag.Size | InvalidationFlag.TextColor |
                    InvalidationFlag.TextColor | InvalidationFlag.Texture))
                {
                    bool flag = (InvalidationMask & ~InvalidationFlag.Visible) != 0;
                    if (flag)
                    {
                        PreLayout();
                        OnLayout();
                    }
                    if (Visuals != null)
                    {
                        foreach (Visual v in Visuals) if (v != null) v.PreLoad();
                    }
                    if (flag && SubControls != null)
                    {
                        if ((CheckFlag(InvalidationFlag.Size| InvalidationFlag.Margin| InvalidationFlag.Padding)))
                        {
                            if (this is ICompositeControl) LayoutEngine.LayoutChildren(this);
                            else
                            {
                                IDecorator decorator = this as IDecorator;
                                if (decorator != null) LayoutEngine.Layout(decorator.Control, Size);
                            }
                        }
                        //bool needRefresh = (InvalidationMask & InvalidationFlag.Bounds) != 0;
                        foreach (Control c in SubControls)
                        {
                            //if (needRefresh) c.Invalidate(InvalidationFlag.Bounds);
                            c.PreLoad();
                        }
                    }
                }
            }
            Loaded();
            bool raiseInitialized =(this.InvalidationMask & InvalidationFlag.Uninitialized) != 0;
            InvalidationMask = InvalidationFlag.Empty;
            if (raiseInitialized) Initialized();
        }

        public void Invalidate(InvalidationFlag flags)
        {
            InvalidationMask |= flags;
            Screen current = Screen.Current;
            if (current != null) current.Invalidate();

        }

        protected virtual void OnTextColorChanged()
        {
        }

        /// <summary>
        /// Sets the input focus to this control, if possible. If the control is not visible or not enabled, it will not receivce focus.
        /// </summary>
        public virtual void Focus()
        {
            Screen.Current.Focus(this);
        }

        /// <summary>
        /// Checks whether the specified <see cref="T:InvalidationFlag"/> is set.
        /// </summary>
        /// <param name="flag">The flag to check.</param>
        /// <returns>True, if set, otherwise false.</returns>
        public bool CheckFlag(InvalidationFlag flag)
        {
            return (InvalidationMask & flag) != 0;
        }

        /// <summary>
        /// Gets the boundaries of the control in absolute pixels relative to the screen.
        /// Note that this is a calcualted value that can take a lot of recursive loops to determine, so cache it if possible!
        /// </summary>
        /// <returns>A rectangle that contains the screen boundaries.</returns>
        public Rectangle ToScreenBounds()
        {
            return Control.Unscale(Screen.Current.ToScreenBounds(this));
        }

        /// <summary>
        /// Gets an IList of all child controls of this control, otherwise null.
        /// </summary>
        /// <returns></returns>
        public IList<Control> GetControls()
        {
            return SubControls;
        }

        /// <summary>
        /// Captures an image of the current state of this control including all child controls.
        /// Dispose the texture that returns if it is no longer required to release texture memory!
        /// </summary>
        /// <returns>A texture that contains the image.</returns>
        public Texture Capture()
        {
           return Screen.Renderer.Capture(this);
        }

        #endregion
        #region Scale Methods

        // Scale Methods are by controls to scale their underlying <see cref="Visual"/>s and/or graphical rendering to fit the physical hardware screen size against the
        // designed size.

        public static int ScaleX(int x)
        {
            if (!Screen.IsScaled) return x;
            float scaleX = Screen.ControlScaleFactor.Width;
            return (int)(scaleX * x);
        }

        public static int ScaleY(int y)
        {
            if (!Screen.IsScaled) return y;
            float scaleY = Screen.ControlScaleFactor.Height;
            return (int)(scaleY * y);
        }

        public static int Scale(int xy)
        {
            if (!Screen.IsScaled) return xy;
            SizeF scale = Screen.ControlScaleFactor;
            return (int)(xy * scale.Width);
        }

        public static Size Scale(Size size)
        {
            if (!Screen.IsScaled) return size;
            SizeF scale = Screen.ControlScaleFactor;
            return new Size((int)(size.Width * scale.Width), (int)(size.Height * scale.Height));
        }

        public static Size Unscale(Size size)
        {
            if (!Screen.IsScaled) return size;
            SizeF scale = Screen.ControlScaleFactor;
            return new Size((int)(size.Width / scale.Width + 0.5f), (int)(size.Height / scale.Height + 0.5f));
        }


        public static Point Unscale(Point p)
        {
            if (!Screen.IsScaled) return p;
            SizeF scale = Screen.ControlScaleFactor;
            return new Point((int)(p.X / scale.Width + 0.5f), (int)(p.Y / scale.Height + 0.5f));
        }

        public static Point Scale(Point p)
        {
            if (!Screen.IsScaled) return p;
            SizeF scale = Screen.ControlScaleFactor;
            return new Point((int)(p.X * scale.Width + 0.5f), (int)(p.Y * scale.Height + 0.5f));
        }

        public static int UnscaleX(int x)
        {
            if (!Screen.IsScaled) return x;
            SizeF scale = Screen.ControlScaleFactor;
            return (int)(x / scale.Width + 0.5f);
        }

        public static int UnscaleY(int y)
        {
            if (!Screen.IsScaled) return y;
            SizeF scale = Screen.ControlScaleFactor;
            return (int)(y / scale.Height + 0.5f);
        }

        public static Size ScaledSize(int width, int height)
        {
            if (!Screen.IsScaled) return new Size(width, height);
            SizeF scale = Screen.ControlScaleFactor;
            return new Size((int)(scale.Width * width), (int)(scale.Height * height));
        }

        public static Point ScaledPoint(int x, int y)
        {
            if (!Screen.IsScaled) return new Point(x, y);
            SizeF scale = Screen.ControlScaleFactor;
            return new Point((int)(x * scale.Width), (int)(y * scale.Height));
        }

        public static Rectangle Scale(Rectangle bounds)
        {
            if (!Screen.IsScaled) return bounds;
            SizeF scale = Screen.ControlScaleFactor;
            float sX = scale.Width;
            float sY = scale.Height;
            return new Rectangle((int)(sX * bounds.X), (int)(sY * bounds.Y), (int)(sX * bounds.Width), (int)(sX * bounds.Height));
        }

        public static Rectangle Unscale(Rectangle bounds)
        {
            if (!Screen.IsScaled) return bounds;
            SizeF scale = Screen.ControlScaleFactor;
            float sX = scale.Width;
            float sY = scale.Height;
            return new Rectangle((int)(bounds.X / sX), (int)(bounds.Y / sY), (int)(bounds.Width / sX), (int)(bounds.Height / sY));
        }

        public static Rectangle ScaledRect(int x, int y, int w, int h)
        {
            if (!Screen.IsScaled) return new Rectangle(x, y, w, h);
            SizeF scale = Screen.ControlScaleFactor;
            float sX = scale.Width;
            float sY = scale.Height;
            return new Rectangle((int)(sX * x), (int)(sY * y), (int)(sX * w), (int)(sX * h));
        }

        public static Rectangle UnscaledRect(int x, int y, int w, int h)
        {
            if (!Screen.IsScaled) return new Rectangle(x, y, w, h);
            SizeF scale = Screen.ControlScaleFactor;
            float sX = scale.Width;
            float sY = scale.Height;
            return new Rectangle((int)(x / sX), (int)(y / sY), (int)(w / sX), (int)(h / sY));
        }

        //TODO: Implement this correctly.
        public static Margin ScaleMargin(int? left, int? top, int? right, int? bottom)
        {
            return new Margin(left, top, right, bottom);
        }


        #endregion
        #region IControlAnimation Members

        bool IControlAnimation.IsAnimated
        {
            get { throw new NotImplementedException(); }
        }

        IList<Transformation> IControlAnimation.AnimationTransformations
        {
            get
            {
                if (animTransforms == null) animTransforms = new List<Transformation>();
                return animTransforms;
            }
        }

        #endregion
        #region Events

        public event EventHandler LocationChanged;
        public event EventHandler SizeChanged;
        public event EventHandler EnabledChanged;
        public event EventHandler VisibleChanged;

        /// <summary>
        /// Occurs when the component is disposed by a call to the System.ComponentModel.Component.Dispose() method.
        /// </summary>
        public event EventHandler Disposed;

        #endregion
    }
}
